import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import util.Couple;




public class CommentRater {
	
	private RatingResult r = new RatingResult();
	
	HashMap<String,Double> reviewScores;
	HashMap<String, Double> goodSentencesScores;
	HashMap<String, Double> badSentencesScores;
	HashMap<String, Double> scores;
	HashMap<String, Double> productScores;
	
	Vector<String> neutralSentencesScores;
	public int opinionatedNb;
	
	

	/**
	 * This method gathers the relevant sentences and rank them by their score.
	 * 
	 * For each input keyword, this method adds as a relevant keywords its synonyms
	 * and antonyms. It then retrieves all sentences containing this relevant keywords.
	 * Using the rateComment() method of the same class, it attributes a score to each
	 * sentence and classifies them into good (score > 0) or bad (score < 0).
	 * 
	 * @param keywords		The keywords extracted from the question
	 * @param productName	The name of the product
	 * @return				A RatingResult object populated with the good,
	 * 						neutral and bad sentences, the best products 
	 * 						and the best comments
	 * 						
	 */
	@SuppressWarnings("unchecked")
	public RatingResult rateComments(Vector<String> keywords, String productName){
		
		
		reviewScores = new HashMap<String, Double>();
		goodSentencesScores = new HashMap<String, Double>();
		badSentencesScores = new HashMap<String, Double>();
		neutralSentencesScores = new Vector<String>();
		scores = new HashMap<String, Double>();
		productScores = new HashMap<String, Double>();
		
		opinionatedNb = 0;
		
		//GET SYNONYMS AND ANTONYMS
		Vector<String> synKeywords = getSynonyms(keywords);
		Vector<String> antKeywords = getAntonyms(keywords);

		
		//allKeywords contains all the words we are looking for in the index
		Vector<String> allKeywords = new Vector<String>(keywords);
		allKeywords.addAll(synKeywords);
		allKeywords.addAll(antKeywords);
		
		//Contains all the sentences we will rate and the product they are related to
		//Example of object in relevantSentences:
		//		("this coffeemachine is made of awesome", Coffeemaker 3000)
		Vector<Couple> relevantSentences = new Vector<Couple>();
		
		//Fill relevantSentences for ALL PRODUCTS
		if(productName.equals("all"))
		{
			for(String word: allKeywords)
			{
				Vector<String> c = QandASystem.index.get(word);
				if(c.size()!=0)
				{
					for(String id : c) {
						Vector<String> sent = DocumentParser.getSentences(id, word);
						String product = DocumentParser.getProductName(id);
						for(String s: sent)
							relevantSentences.add(new Couple(s,product));
					}
				}
			}
		}
		//End fill relevantSentences for ALL PRODUCT
		
		//Fill relevantSentences for A SINGLE PRODUCT
		else
		{
			for(String word: allKeywords)
			{
				Vector<String> c = QandASystem.index.get(word);
				if(c.size()!=0)
				{
					for(String id : c) {
						if(DocumentParser.getProductName(id).equals(productName))
						{
							Vector<String> sent = DocumentParser.getSentences(id, word);
							String product = DocumentParser.getProductName(id);
							for(String s: sent)
								relevantSentences.add(new Couple(s,product));
						}
						else;
					}
				}
			}
		}
		//End fill relevantSentences for A SINGLE PRODUCT
		
		
		//ENSURE THAT EACH SENTENCE IS SCANNED ONLY ONCE
		Set set = new HashSet(relevantSentences); //Sets doesn't accept two times the same element
		relevantSentences.clear();
		relevantSentences.addAll(set);
		
		
		rateComment(relevantSentences, keywords, synKeywords, antKeywords);

		System.out.println("Opinionated sentences: "+opinionatedNb);
		
		//Fill the RatingResult ###################################################################
		// SortByValue : sort from the lowest score to the highest
		Vector<String> goodSentences = new Vector<String>(sortByValue(goodSentencesScores).keySet());
		Collections.reverse(goodSentences); //The best sentences are at the beginning of the vector
		Vector<String> badSentences = new Vector<String>(sortByValue(badSentencesScores).keySet());
		
		Vector<String> bestReviews = new Vector<String>(sortByValue(reviewScores).keySet());
		Collections.reverse(bestReviews); //The best reviews are at the beginning of the vector
		
		Vector<String> bestProducts = new Vector<String>(sortByValue(productScores).keySet());
		Collections.reverse(bestProducts); //The best products are at the beginning of the vector
		
		r.setGoodSentences(goodSentences);
		r.setBadSentences(badSentences);
		r.setNeutralSentences(neutralSentencesScores);
		r.setBestComment(bestReviews);
		r.setBestProducts(bestProducts);
		//END Fill the RatingResult ##############################################################
		
		return r;
	}

	
	
	/**
	 * This method rates a comment based on the keywords, its synonyms and antonyms.
	 * 
	 * The rating rules are as follow:<br>
	 *	- If a keyword is found in the sentence, +3;<br>
	 *	- If a synonym of the keywords is found, +1;<br>
	 *	- If an antonym of the keywords is found, -1;<br>
	 *	- If a keyword is found after a NOT word, -3;<br>
	 *	- If a synonym is found after a NOT word, -1;<br>
	 *	- If an antonym is found after a NOT word, +1;<br>
	 *	- Each new occurrence of an already present keyword, +1 instead of +3;<br>
	 * 
	 * @param relevantSentences	The sentences that contains the keywords
	 * @param keywords			The keywords extracted from the user's question
	 * @param synKeywords		The synonyms of the keywords
	 * @param antKeywords		The antonyms of the keywords
	 * @return					The score for the comment (> 0 for good comment, < 0 for bad comment
	 * 							and 0 for neutral comment)
	 */
	private Double rateComment(Vector<Couple> relevantSentences, Vector<String> keywords, Vector<String> synKeywords,
			Vector<String> antKeywords){
		
		double keyWordCount = 0;
		double totalCommentScore = 0;
		
		
		int k=1;
		for(Couple sentence: relevantSentences){
			Vector<String> tempKeywords = new Vector<String>(keywords);
			Vector<String> tempSynonyms = new Vector<String>(synKeywords);
			
			
			
			//HERE WE COMPUTE THE SCORE FOR EACH SENTENCE ###############################################
			double sentenceScore = 0;
			boolean opinionatedSentence = false;
			
			int not = 0;
			Vector<String> words = Parser.parse(sentence.getFirst());
			
			
			Vector<String> relevantWords = new Vector<String>();
			for(int i = 0; i < words.size(); i++) {
				if(keywords.contains(Stemmer.stem(words.get(i))) || 
						synKeywords.contains(Stemmer.stem(words.get(i)))||
						antKeywords.contains(Stemmer.stem(words.get(i))))
					for(int j = i - 5; j <= i + 5; j++)
						if(j >= 0 && j < words.size()) {
							if(relevantWords.contains(words.get(j)))
								j += 5;
							else
								relevantWords.add(words.get(j));
						}
			}
			
			for(String word:relevantWords){
				word = word.toLowerCase();
				
				//Handle of the "NOT" case
				if(word.equals("not") || word.equals("isn't") || word.equals("doesn't") || 
										 word.equals("don't") || word.equals("aren't")		)
					{
						not = 2;
					}
				
				//If the keywords are found in the sentence
				else if(tempKeywords.contains(Stemmer.stem(word))){
					if(not!=0) //If there is a negative marker before, the score is NEG
					{
						sentenceScore-=3;
						not --;
					}
					else //Else it's POS
					{
						sentenceScore+=3;
					}
					tempKeywords.remove(word);
					tempSynonyms.add(word);
					keyWordCount++;
					opinionatedSentence = true;
				}
				//If synonyms of the keywords are found in the sentence
				else if(tempSynonyms.contains(Stemmer.stem(word))){
					if(not!=0) //If there is a negative marker before, the score is NEG
					{
						if(keywords.contains(Stemmer.stem(word)))
						{
							sentenceScore -= 3;
						}
						else
							sentenceScore --;
						not --;
					}
					else //Else it's POS
					{
						sentenceScore++;
					}
					keyWordCount++;
					opinionatedSentence = true;
				//If antonyms of the keywords are found in the sentence
				}else if (antKeywords.contains(Stemmer.stem(word))){
					if(not!=0) //If there is a negative marker before, the score is POS
					{
						sentenceScore+=1;
						not --;
					}
					else //Else it's NEG
					{
						sentenceScore --;
					}
					keyWordCount++;
					opinionatedSentence = true;
				}
				else
				{
					if(not != 0)
						not --;
				}
			}
			//End of the computation of the SCORE #########################################################
			
			
			
			
			//Fill the vectors ####################################################
			if(opinionatedSentence && sentenceScore>0)
			{
				goodSentencesScores.put(sentence.getFirst(), sentenceScore);
				totalCommentScore+= sentenceScore;
				opinionatedNb += 1;
			}
			else if(opinionatedSentence && sentenceScore<0)
			{
				badSentencesScores.put(sentence.getFirst(), sentenceScore);
				totalCommentScore+= sentenceScore;
				opinionatedNb += 1;
			}
			else if(opinionatedSentence && sentenceScore==0.0)
			{
				neutralSentencesScores.add(sentence.getFirst());
				opinionatedNb += 1;
			}
			//END Fill the vectors ###############################################
			
			//Fill the productScores
			if(productScores.containsKey(sentence.getSecond()))
			{
				Double oldVal = productScores.get(sentence.getSecond());
				productScores.remove(sentence.getSecond());
				oldVal+=sentenceScore;
				productScores.put(sentence.getSecond(), sentenceScore);
			}
			else
				productScores.put(sentence.getSecond(), sentenceScore);
			//END Fill the productScores
			k++;
		}
		
		if(keyWordCount!=0){
			return totalCommentScore/keyWordCount;
		}else{
			return 0.0;
		}
	}
	
	/**
	 * Method that retrieves antonyms of a word
	 * @param keywords: list of words that we want the antonyms
	 * @return a list of antonyms
	 */
	private Vector<String> getAntonyms(Vector<String> keywords) {
		Vector<String> w = new Vector<String>();
		for(String word : keywords)
		{
			w.addAll(WordNet.getAntonyms(word));
		}
		return w;
	}

	
	/**
	 * Method that retrieves synonyms of a word
	 * @param keywords: list of words that we want the synonyms
	 * @return a list of synonyms
	 */
	private Vector<String> getSynonyms(Vector<String> keywords) {
		Vector<String> w = new Vector<String>();
		for(String word : keywords)
		{
			w.addAll(WordNet.getSynonyms(word));
		}
		return w;
	}
	
	public int getOpinionatedNb()
	{
		return opinionatedNb;
	}
	
	//############# MAIN ##########################################################
//	public static void main(String[] args) throws Exception {
//		
//		CommentRater c = new CommentRater();
//		
//		Vector<String> vect = new Vector<String>();
//		vect.add("best");
//		vect.add("quality");
//		vect.add("price");
//
//		
//		RatingResult r = c.rateComments(vect);
//		
//		System.out.println("Total number of sentences: "+c.sentenceNb);
//		System.out.println("Number of opinionated sentences: "+c.opinionatedNb);
//		
//		System.out.println("Number of POS sentences: "+c.goodSentencesScores.size());
//		System.out.println("Number of NEG sentences: "+c.badSentencesScores.size());
//		System.out.println("Number of NEU sentences: "+c.neutralSentencesScores.size());
//		
//		r.displayGoodSentences(5);
//		r.displayBadSentences(3);
//		r.displayNeutralSentences(1);
//		
//	}
	
	
	
	/**
	 * Utility function that allows to sort the elements of a hashmap
	 * depending on their values
	 * @param map
	 * @return a sorted hashmap
	 */
	@SuppressWarnings("unchecked")
	static Map sortByValue(Map map) {
		List list = new LinkedList(map.entrySet());
		Collections.sort(list, new Comparator() {
			public int compare(Object o1, Object o2) {
				return ((Comparable) ((Map.Entry) (o1)).getValue())
				.compareTo(((Map.Entry) (o2)).getValue());
			}
		});
		// logger.info(list);
		Map result = new LinkedHashMap();
		for (Iterator it = list.iterator(); it.hasNext();) {
			Map.Entry entry = (Map.Entry)it.next();
			result.put(entry.getKey(), entry.getValue());
		}
		return result;
	}

}
